from ctypes import *
import numpy as np 
import wave

class MFCC():
    def __init__(self, mfcc_lib_path):   
        self.mfcc_func = None
        self.mfcc_len = 49 * 10
        self.load_library(mfcc_lib_path)
        
    def load_library(self, path):
        global mfcc_func
        mfcc_lib = cdll.LoadLibrary(path)
        self.mfcc_func = mfcc_lib.AUDIO_PreprocessSample
        self.mfcc_func.argtypes = [POINTER(c_short)]
        self.mfcc_func.restype = POINTER(c_float)

    def calc_mfcc(self, wav_in, mfcc_out=None, audioBlock=1):
        assert(type(wav_in) == np.ndarray)
        wav_in_fp = cast(wav_in.ctypes.data, POINTER(c_short)) #cast((c_short * len(wav_in))(*(wav_in.tolist())), POINTER(c_short))
        mfcc_out_p = c_void_p(None) if (type(mfcc_out) == type(None)) else cast(mfcc_out.ctypes.data, POINTER(c_float))#cast((c_float * len(mfcc_out))(*(mfcc_out.tolist())), POINTER(c_float))
        
        # mfcc_np = np.zeros(mfcc_len, dtype='float32')
        
        # call function
        mfcc_return_p = self.mfcc_func(wav_in_fp, mfcc_out_p, audioBlock << 16 | 1) # high_16: how much audios, low_16: the real audioblock
        # for i in range(mfcc_len):
        #     mfcc_np[i] = mfcc_return_p[i]
        
        # mfcc_np = np.ctypeslib.as_array(mfcc_out_p, shape=(1, mfcc_len)) # no need to do this, there is memory copy, a pointer assign
        return mfcc_out

    def process_wav_batchs(self, wave_file_paths, gausse=0, shift=0):
        mfcc_results = np.zeros((len(wave_file_paths), self.mfcc_len), dtype='float32')
        audio_signals = []
        # only handle 1s audio
        for path in wave_file_paths:
            audio = wave.open(path)
            signal = audio.readframes(-1)       
            signal = np.fromstring(signal, 'int16')
            if(len(signal) <= 16000):
                signal = np.append(signal, [0]*(16000 - len(signal))).astype('int16')
            if(gausse != 0) or (shift != 0):
                from data_augment import add_noise, shift_audio 
                # A fake random to do the augment
                random_key = np.random.randint(100)
                if(random_key > 50):
                    random_shift = min(max(shift + np.random.randint(-shift, shift), 0), len(signal) - 1)
                    shift_audio(signal, random_shift)
                random_key = np.random.randint(100)
                if(random_key > 50): 
                    random_gausse = gausse + np.random.randint(-gausse, gausse)
                    signal = add_noise(signal, SNR=gausse)
            audio_signals += [signal]
        audio_signals = np.asarray(audio_signals)
        self.calc_mfcc(audio_signals, mfcc_results, len(audio_signals))
        return mfcc_results

        
    def process_wav(self, wave_file_path, mfcc_result):
        audio = wave.open(wave_file_path)
        signal = audio.readframes(-1)
        
        signal = np.fromstring(signal, 'int16')  
        if(len(signal) <= 16000):
            signal = np.append(signal, [0]*(16000 - len(signal))).astype('int16')
        else:
            signal_slice = len(signal) // 16000
            signal = signal[:signal_slice*16000]
            # re-build a mfcc_result
            mfcc_result = np.zeros((signal_slice, self.mfcc_len), dtype='float32')
        
        self.calc_mfcc(signal, mfcc_result, len(signal) // 16000)
     
        return mfcc_result 
    
if __name__ == "__main__":
    mfcc = MFCC(r"C:\Users\nxf48054\source\repos\mfcc_pc_dll\x64\Release\mfcc_pc_dll.dll")



    